package xin.core.jwt;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.mobile.device.Device;
import org.springframework.stereotype.Service;
import xin.core.AppConstant;
import xin.core.ErrorConstant;
import xin.core.exception.SysException;
import xin.lms.model.Jwtlog;
import xin.lms.repository.JwtlogRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.transaction.annotation.Transactional;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Map;
import java.util.UUID;

/**
 * <pre>
 * Jwt Operate Util
 * </pre>
 *
 * @author lixin
 * @version 1.0
 * @since 2017/11/23 9:50
 */
@Service
@Slf4j
@Transactional(readOnly = true)
public class JwtTokenService implements Serializable {

    static final String CLAIM_KEY_USERNAME = "sub";
    static final String CLAIM_KEY_AUDIENCE = "aud";
    static final String CLAIM_KEY_CREATED = "iat";

    static final String AUDIENCE_UNKNOWN = "unknown";
    static final String AUDIENCE_WEB = "web";
    static final String AUDIENCE_MOBILE = "mobile";
    static final String AUDIENCE_TABLET = "tablet";

    @Autowired
    private JwtlogRepository jwtlogRepository;

    @Value("${jwt.expiration}")
    private Long expiration;

    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    public Date getIssuedAtDateFromToken(String token) {
        Date issuedAt;
        try {
            final Claims claims = getClaimsFromToken(token);
            issuedAt = claims.getIssuedAt();
        } catch (Exception e) {
            issuedAt = null;
        }
        return issuedAt;
    }

    public String getAudienceFromToken(String token) {
        String audience;
        try {
            final Claims claims = getClaimsFromToken(token);
            audience = claims.getAudience();
        } catch (Exception e) {
            audience = null;
        }
        return audience;
    }

    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    public String getLoginId(String token) {
        String logId;
        try {
            final Claims claims = getClaimsFromToken(token);
            logId = (String) claims.get(AppConstant.LOGIN_LOG_ID);
        } catch (Exception e) {
            logId = null;
        }
        return logId;
    }

    public String getUserId(String token) {
        String userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            userId = (String) claims.get(AppConstant.USER_ID);
        } catch (Exception e) {
            throw new SysException(ErrorConstant.SYS_REGIST,"无效的令牌");
        }
        return userId;
    }

    public String getJwtId(String token) {
        String logId;
        try {
            final Claims claims = getClaimsFromToken(token);
            logId = (String) claims.get(AppConstant.JWT_ID);
        } catch (Exception e) {
            logId = null;
        }
        return logId;
    }

    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(AppConstant.JWT_SECRET)
                    .parseClaimsJws(token)
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    public Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    public Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    private String generateAudience(Device device) {
        String audience = AUDIENCE_UNKNOWN;
        if (device.isNormal()) {
            audience = AUDIENCE_WEB;
        } else if (device.isTablet()) {
            audience = AUDIENCE_TABLET;
        } else if (device.isMobile()) {
            audience = AUDIENCE_MOBILE;
        }
        return audience;
    }

    private Boolean ignoreTokenExpiration(String token) {
        String audience = getAudienceFromToken(token);
        return (AUDIENCE_TABLET.equals(audience) || AUDIENCE_MOBILE.equals(audience));
    }

    @Transactional
    public String generateToken(UserDetails userDetails, Device device, Map<String, Object> claims) {
        return doGenerateToken(claims, userDetails.getUsername(), generateAudience(device));
    }

    /**
     * 生成token 并入库
     * @param claims claims
     * @param subject subject
     * @param audience audience
     * @return token
     */
    public String doGenerateToken(Map<String, Object> claims, String subject, String audience) {
        log.info("jwt token generate");
        final String id = UUID.randomUUID().toString().replace("-", "");

        claims.put(AppConstant.JWT_ID,id);
        final Date issuedAt = new Date();
        final Date expirationDate = calculateExpirationDate(issuedAt);
        final String token = Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setAudience(audience)
                .setIssuedAt(issuedAt)
                .setIssuer(AppConstant.JWT_ISSUER)
                .setExpiration(expirationDate)
                .signWith(SignatureAlgorithm.HS512, AppConstant.JWT_SECRET)
                .compact();

        Jwtlog log = Jwtlog.builder()
                .id(id)
                .secret(AppConstant.JWT_SECRET)
                .token(token)
                .tokenStatus(Jwtlog.TokenStatus.NORMAL.getValue())
                .tokenTime(LocalDateTime.now())
                .build();
        jwtlogRepository.save(log);
        return token;
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getIssuedAtDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token) || ignoreTokenExpiration(token));
    }

    @Transactional
    public String refreshToken(String token) {
        log.info("jwt token refresh");
        final Date createdDate = new Date();
        final Date expirationDate = calculateExpirationDate(createdDate);

        final Claims claims = getClaimsFromToken(token);
        claims.setIssuedAt(createdDate);
        claims.setExpiration(expirationDate);
        final String result = Jwts.builder()
                .setClaims(claims)
                .signWith(SignatureAlgorithm.HS512, AppConstant.JWT_SECRET)
                .compact();

        Jwtlog jwtlog = jwtlogRepository.findOne(getJwtId(token)) ;
        jwtlog.setToken(token);
        jwtlog.setTokenTime(LocalDateTime.now());
        jwtlog.setSecret(AppConstant.JWT_SECRET);
        jwtlog.setTokenStatus(Jwtlog.TokenStatus.REFRESH.getValue());
        jwtlogRepository.save(jwtlog);

        return result;
    }


    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 1000);
    }


//    public SecretKey generalKey(){
//        byte[] encodedKey = Base64.decodeBase64(AppConstant.JWT_SECRET);
//        SecretKey key = new SecretKeySpec(encodedKey, 0, encodedKey.length, "AES");
//        return key;
//    }
}